<?php
#
# dmBridge: a data access framework for CONTENTdm(R)
#
# Copyright © 2009, 2010, 2011 Board of Regents of the Nevada System of Higher
# Education, on behalf of the University of Nevada, Las Vegas
#

/**
 * Controller for "object view" in the template engine.
 *
 * @author Alex Dolski <alex.dolski@unlv.edu>
 * @license http://www.opensource.org/licenses/mit-license.php
 */
class DMTEObjectController extends DMAbstractController {

	/** @var DMObjectQuery */
	private $query;

	/**
	 * Outputs the comments Atom feed.
	 *
	 * @param string aliasptr Comma-separated alias/pointer pair
	 */
	public function comments($aliasptr) {
		$tmp = explode(",", $aliasptr);
		$col = DMCollectionFactory::getCollection(
				DMCollection::getSanitizedAlias($tmp[0]));
		$ptr = (int) $tmp[1];

		try {
			// make sure we aren't viewing something we shouldn't be
			if (!is_numeric($ptr)) {
				throw new DMUnavailableModelException(
					DMLocalizedString::getString("NONEXISTENT_OBJECT"));
			}
			$obj = DMObjectFactory::getObject($col, $ptr);

			$response = new DMHTTPResponse();
			$rep = new DMHTTPRepresentation();
			$rep->setMediaType(new DMMediaType("application", "atom+xml"));
			$trans = new DMAtomRepresentationTransformer();
			$rep->setBody($trans->transformObjectComments($obj));
			$response->setRepresentation($rep);
			$response->send();
			die;
		} catch (DMException $e) {
			require_once($this->getTemplateSet()->getErrorTemplate()
					->getAbsolutePathname());
			die;
		}
	}

	/**
	 * Handles results view, which encompasses both browse view and search view.
	 *
	 * @param alias A collection alias, as a string. If invalid or missing,
	 * a either the template set's default collection or the dmBridge default
	 * collection will be used.
	 * @since 0.1
	 */
	public function index($alias) {
		$alias = DMCollection::getSanitizedAlias($alias);
		if (strlen($alias) < 2) {
			$alias = "/dmdefault";
		}

		$response = new DMHTTPResponse();
		$rep = DMHTTPResponseFactory::getRepresentation();
		$trans = DMHTTPResponseFactory::getTransformer();
		$response->setRepresentation($rep);

		try {
			$ts = $this->getTemplateSet();
			if (!$ts) {
				throw new DMUnavailableModelException(
						DMLocalizedString::getString("INVALID_TPL_SET"));
			}

			$specified_collection = $displayed_collection
			= DMCollectionFactory::getCollection($alias);

			if (!$specified_collection) {
				throw new DMUnavailableModelException(
						DMLocalizedString::getString("INVALID_COLLECTION"));
			}

			if ($specified_collection->isDefault()) {
				$input = $this->getSession()->getSearchInput();
				if (!$input || !$input->isPresent()) {
					// Set the current collection to the current template set's
					// default collection so that we have something to view
					if ($ts->getDefaultCollection()) {
						$displayed_collection = $ts->getDefaultCollection();
					} else {
						throw new DMUnavailableModelException(
							DMLocalizedString::getString("NO_ALIAS_SUPPLIED"));
					}
				}
			}
			// make sure we aren't viewing something we shouldn't be
			if ($ts && !$ts->isAuthorizedToViewCollection(
					$displayed_collection)) {
				throw new DMSecurityException(
					DMLocalizedString::getString("ACCESS_DENIED_TO_COL"));
			}

			if (!$this->getHTTPRequest()->getURI()->getExtension()
					&& $rep->getMediaType()->equals(
							new DMMediaType("application", "xml"))) {
				$rep->setMediaType(new DMMediaType("text", "html"));
			}

			// check for user input from a search
			$uri = $this->getHTTPRequest()->getURI();
			$this->getSession()->setSearchInput(new DMInput($uri));
			if ($this->getSession()->getSearchInput()->isPresent()) {
				$this->search($displayed_collection, $rep);
			} else {
				$this->browse($displayed_collection, $rep);
			}
			$this->query->getSearchResults();

			// figure out which view template to load
			$path = (count($this->query->getFacetTerms()))
					? "/templates/object/results_faceted.html.php"
					: "/templates/object/results.html.php";
			$path = $this->query->getNumResults()
					? $path : "/templates/object/no_results.html.php";

			$tpl = $ts->getTemplateAtPathname($path);
			global $view;
			$view = new DMResultsView($tpl,
					$this->getSession(),
					$specified_collection, $this->query);

			if ($rep->getMediaType()->equals(new DMMediaType("text", "html"))) {
				// store results page url in session to facilitate "back to
				// results" link
				$this->getSession()->setResultsView($view);
			} else {
				$rep->setBody($trans->transformResults($this->query));
				$response->send();
				die;
			}
		} catch (DMException $e) {
			if (!$this->getTemplateSet()) {
				throw $e;
			}
			$tpl = $this->getTemplateSet()->getTemplateAtPathname(
					"/templates/error/view.html.php");
			global $view;
			$view = new DMSystemErrorView($tpl,
					$this->getSession(), $e);
			return;
		}
	}

	/**
	 * Invoked by index().
	 */
	private function browse(DMCollection $col, DMHTTPRepresentation $rep) {
		$sort = $order = $page = $rpp = null;
		$uri = $this->getHTTPRequest()->getURI();
		$rpp = $this->getTemplateSet()->getNumResultsPerPage();
		if (!$rpp) {
			$rpp = $uri->getQueryValue("rpp");
			$rpp = ($rpp < 10) ? 20 : $rpp;
			$rpp = ($rpp > 50) ? 50 : $rpp;
		}
		if ($rep->getMediaType()->equals(
				new DMMediaType("application", "atom+xml"))) {
			$sort = "dmmodified";
			$order = "desc";
			$page = 1;
		} else if ($rep->getMediaType()->equals(
				new DMMediaType("application", "xml"))) {
			$sort = "dmmodified";
			$order = "desc";
			$page = abs($uri->getQueryValue("page"));
			$rpp = 50;
		} else { // DMObjectQuery setters will handle remaining validation
			$sort = $uri->getQueryValue("sort");
			$order = $uri->getQueryValue("order");
			$page = abs($uri->getQueryValue("page"));
		}

		// browse mode should wipe any saved query
		$this->getSession()->unsetQuery();
		// as well as any saved search input
		$this->getSession()->unsetSearchInput();

		$this->query = new DMObjectQuery();
		$this->query->setCollections(array($col));
		$this->query->setSortFields(array($sort => $order));
		$this->query->setPage($page);
		$this->query->setNumResultsPerPage($rpp);
		/* cdm doesn't return facets in browse view
		foreach ($col->getFacets() as $term) {
			$this->query->addFacetTerm($term);
		} */

	}

	/**
	 * Invoked by index().
	 */
	private function search(DMCollection $col, DMHTTPRepresentation $rep) {
		// if there are any collection aliases in the search that we do not have
		// access to, bail out
		try {
			if (!$this->isAuthorizedToSearch()) {
				throw new DMSecurityException(
					DMLocalizedString::getString("ACCESS_DENIED_TO_COL"));
			}
		} catch (DMSecurityException $e) {
			require_once($this->getTemplateSet()->getErrorTemplate()
					->getAbsolutePathname());
			die;
		}

		try {
			$this->getSession()->getSearchInput()->validate();
		} catch (DMIllegalArgumentException $e) {
			$this->getSession()->setFlash(
					new DMFlash($e->getMessage(), false));
		}

		$uri = $this->getHTTPRequest()->getURI();
		$sort = $order = $page = $rpp = null;
		if ($rep->getMediaType()->equals(new DMMediaType("application", "atom+xml"))) {
			$sort = "dmmodified";
			$order = "desc";
			$page = 1;
			$rpp = 30;
		} else { // DMObjectQuery setters will handle validation
			$sort = $uri->getQueryValue("sort");
			$order = $uri->getQueryValue("order");
			$page = $uri->getQueryValue("page");
			$rpp = $this->getTemplateSet()->getNumResultsPerPage();
			if (!$rpp) {
				$rpp = $uri->getQueryValue("rpp");
				$rpp = ($rpp < 10) ? 20 : $rpp;
				$rpp = ($rpp > 50) ? 50 : $rpp;
			}
		}

		$this->query = new DMObjectQuery();
		$this->query->setCollections(
				$this->getSession()->getSearchInput()->getCollections());
		$this->query->setPredicates(
				$this->getSession()->getSearchInput()->getTerms());
		$this->query->setSortFields(array($sort => $order));
		$this->query->setPage($page);
		$this->query->setNumResultsPerPage($rpp);
		$this->getSession()->setQuery($this->query);
		foreach ($this->getSession()->getSearchInput()->getCollections() as $c) {
			foreach ($c->getFacets() as $term) {
				$this->query->addFacetTerm($term);
			}
		}
	}

	protected function isAuthorizedToSearch() {
		if (!$this->getSession()->getSearchInput()
				instanceof DMInput) {
			return true;
		}
		foreach ($this->getSession()->getSearchInput()
				->getCollections() as $c) {
			if (!$this->getTemplateSet()->isAuthorizedToViewCollection($c)) {
				return false;
			}
		}
		return true;
	}

	/**
	 * @param string aliasptr Comma-separated alias/pointer pair
	 */
	public function compoundSearch($aliasptr) {
		$tmp = explode(",", $aliasptr);
		$col = DMCollectionFactory::getCollection(
				DMCollection::getSanitizedAlias($tmp[0]));
		$ptr = (int) $tmp[1];
		$uri = $this->getHTTPRequest()->getURI();

		$page = (int) substr($uri->getQueryValue("page"), 0, 4);
		$rpp = (int) $uri->getQueryValue("rpp");
		$rpp = ($rpp > 100) ? 100 : $rpp;
		$rpp = ($rpp < 10) ? 10 : $rpp;

		try {
			// instantiate an object so we can find its parent to search within
			$obj = DMObjectFactory::getObject($col, $ptr);
			$parent = $obj->getParent() ? $obj->getParent() : $obj;

			$qt = new DMQueryPredicate();
			$qt->setString($uri->getQueryValue("term"));
			$qt->setField(new DMDCElement("full"));
			$qt->setMode("exact");

			$this->query = new DMObjectQuery();
			$this->query->setPage($page);
			$this->query->setNumResultsPerPage($rpp);
			$this->query->addPredicate($qt);
			$this->query->addCollection($col);
			$this->query->addObject($parent);
			$this->query->setSortFields(array("title" => "asc"));

			// need to run getSearchResults() BEFORE getNumResults()
			$this->query->getSearchResults();
			if ($this->query->getNumResults() < 1) {
				$this->getSession()->setFlash(new DMFlash(
						DMLocalizedString::getString("NO_RESULTS_FOR_SEARCH")));
				DMHTTPRequest::redirectToParams("objects" . $col->getAlias() . "/" . $ptr);
				die;
			}

			$ts = $this->getTemplateSet();
			if (!$ts) {
				throw new DMUnavailableModelException(
						DMLocalizedString::getString("INVALID_TPL_SET"));
			}
			$path = "/templates/object/results.html.php";
			$tpl = $ts->getTemplateAtPathname($path);
			global $view;
			$view = new DMObjectResultsView($tpl,
					$this->getSession(), $obj, $this->query);

			$rep = DMHTTPResponseFactory::getRepresentation();
			if (!$rep->getMediaType()->equals(new DMMediaType("text", "html"))) {
				$response = new DMHTTPResponse();
				$trans = DMHTTPResponseFactory::getTransformer();
				$response->setRepresentation($rep);
				$rep->setBody($trans->transformObjectResults($this->query));
				$response->send();
				die;
			}
		} catch (DMException $e) {
			$tpl = $this->getTemplateSet()->getTemplateAtPathname(
					"/templates/error/view.html.php");
			global $view;
			$view = new DMSystemErrorView($tpl,
					$this->getSession(), $e);
		}
	}

	/**
	 * @param string aliasptr Comma-separated alias/pointer pair
	 */
	public function view($aliasptr) {
		$tmp = explode(",", $aliasptr);
		$col = DMCollectionFactory::getCollection(
				DMCollection::getSanitizedAlias($tmp[0]));
		try {
			if (!is_numeric($tmp[1]) || $tmp[1] != round($tmp[1])) {
				throw new DMUnavailableModelException(
						DMLocalizedString::getString("ACCESS_DENIED_TO_OBJ"));
			}
			$ptr = $tmp[1];

			$ts = $this->getTemplateSet();
			if (!$ts) {
				throw new DMUnavailableModelException(
						DMLocalizedString::getString("INVALID_TPL_SET"));
			}
			$allowed_collections = $ts->getAuthorizedCollections();
			$locked_alias = null;
			if (count($allowed_collections) == 1
					&& strlen($allowed_collections[0]) > 1) {
				$locked_alias = $allowed_collections[0];
			}

			// make sure we aren't viewing something we shouldn't be
			if (($locked_alias && $col->getAlias() != $locked_alias)
					|| !$ts->isAuthorizedToViewCollection($col)) {
				throw new DMSecurityException(
					DMLocalizedString::getString("ACCESS_DENIED_TO_OBJ"));
			}

			// object view should wipe any saved search
			$this->getSession()->unsetSearchInput();

			$obj = DMObjectFactory::getObject($col, $ptr);

			// Render compound or single template depending on what the object is
			$path = ($obj->isCompound() || $obj->isChild())
					? "/templates/object/view_compound.html.php"
					: "/templates/object/view_simple.html.php";
			$tpl = $this->getTemplateSet()->getTemplateAtPathname($path);
			global $view;
			$view = new DMObjectView($tpl,
					$this->getSession(), $obj);
			$view->setCommentForm(new DMObjectCommentForm());
			$view->setTagForm(new DMTagForm());

			// check for incoming comments/tags
			$req = $this->getHTTPRequest();
			$rep = $req->getRepresentation();
			if ($req->getMethod() == DMHTTPMethod::POST) {
				switch ($rep->getFormValue("action")) {
				case "comment":
					$this->setForm($view->getCommentForm());
					$this->postComment($obj);
					break;
				case "rate":
					$this->rate($obj);
					break;
				case "tag":
					$this->setForm($view->getTagForm());
					$this->postTag($obj);
					break;
				}
			}

			// Add the object to the list of recently viewed objects
			$this->getSession()->addRecentlyViewedObject($obj);

			$rep = DMHTTPResponseFactory::getRepresentation();
			if (!$rep->getMediaType()->equals(new DMMediaType("text", "html"))) {
				$response = new DMHTTPResponse();
				$trans = DMHTTPResponseFactory::getTransformer();
				$response->setRepresentation($rep);
				$rep->setBody($trans->transformObject($obj));
				$response->send();
				die;
			}
		} catch (DMException $e) {
			$tpl = $this->getTemplateSet()->getTemplateAtPathname(
					"/templates/error/view.html.php");
			global $view;
			$view = new DMSystemErrorView($tpl, $this->getSession(), $e);
		}
	}

	private function postComment(DMObject $obj) {
		// disallow repeat comment submissions
		if ($this->getSession()->hasCommentedOnObject($obj)) {
			$this->getSession()->setFlash(new DMFlash(
					DMLocalizedString::getString("MULTIPLE_COMMENTS_NOT_ALLOWED"),
					false));
			header("HTTP/1.1 403 Forbidden");
		}

		$key = (DMConfigXML::getInstance()->isCommentModerationEnabled())
				? "MODERATED_COMMENT_ADDED" : "COMMENT_ADDED";
		$this->handleForm(DMLocalizedString::getString($key));
	}

	private function postTag(DMObject $obj) {
		$str = (DMConfigXML::getInstance()->isTagModerationEnabled())
				? "MODERATED_TAG_ADDED" : "TAG_ADDED";
		$this->handleForm(DMLocalizedString::getString($str));
	}

	private function rate(DMObject $obj) {
		$rep = $this->getHTTPRequest()->getRepresentation();
		try {
			// check for a valid max rating for weighting the submitted rating
			$rating = new DMRating(
				$rep->getFormValue("value"), $rep->getFormValue("max"));
			// check for text in hidden field
			if (strlen($rep->getFormValue("fauxrating")) > 0) {
				throw new DMIllegalArgumentException(
					DMLocalizedString::getString("FAUX_COMMENT_FAIL"), false);
			}

			// has the user already rated this object?
			if ($this->getSession()->hasRatedObject($obj)) {
				$this->getSession()->setFlash(new DMFlash(
						DMLocalizedString::getString("ALREADY_RATED")));
			}

			// gauntlet cleared, post
			DMDataStoreFactory::getDataStore()->addObjectRating($obj, $rating);
			$this->getSession()->addHasRated($obj);
			$this->getSession()->setFlash(new DMFlash(
					DMLocalizedString::getString("RATING_ADDED"), true));
			DMHTTPRequest::reload();
			die;
		} catch (Exception $e) {
			$this->getSession()->setFlash(
					new DMFlash($e->getMessage(), false));
		}
	}

}
